﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Animation;
using System.Runtime;

namespace System.Windows.Media.Media3D10
{
    /// <summary>
    /// Anime la valeur d'une propriété <see cref="SlimDX.Vector3"/> entre deux valeurs cibles à l'aide d'une interpolation linéaire sur un <see cref="Duration"/> spécifié. 
    /// </summary>
    public class Vector3Animation : Vector3AnimationBase
    {
        #region Dependency Properties
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="By"/>.
        /// </summary>
        public static readonly DependencyProperty ByProperty =
            DependencyProperty.Register("By", typeof(SlimDX.Vector3?), typeof(Vector3Animation));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="EasingFunction"/>.
        /// </summary>
        public static readonly DependencyProperty EasingFunctionProperty =
            DependencyProperty.Register("EasingFunction", typeof(IEasingFunction), typeof(Vector3Animation));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="From"/>.
        /// </summary>
        public static readonly DependencyProperty FromProperty =
            DependencyProperty.Register("From", typeof(SlimDX.Vector3?), typeof(Vector3Animation));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="To"/>.
        /// </summary>
        public static readonly DependencyProperty ToProperty =
            DependencyProperty.Register("To", typeof(SlimDX.Vector3?), typeof(Vector3Animation));
        #endregion
        
        #region Constructors
        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="Vector3Animation"/>
        /// </summary>
        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
        public Vector3Animation()
        {
            this.FillBehavior = FillBehavior.HoldEnd;
        }

        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="Vector3Animation"/> avec la valeur de destination et la durée <see cref="Duration"/> spécifiées.
        /// </summary>
        /// <param name="toValue">Valeur de destination de l'animation.</param>
        /// <param name="duration">
        /// Durée nécessaire à l'animation pour accomplir son cycle en une seule fois.
        /// Pour plus d'informations, consultez la propriété <see cref="Duration"/>.
        /// </param>
        public Vector3Animation(SlimDX.Vector3 toValue, Duration duration)
            : this (toValue, duration, FillBehavior.HoldEnd)
        { }

        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="Vector3Animation"/> qui passe à la valeur spécifiée sur une durée donnée et dont le comportement de remplissage correspond à celui défini.
        /// La valeur de début de l'animation correspond à la valeur de base de la propriété en cours d'animation ou à la valeur de sortie d'une autre animation.
        /// </summary>
        /// <param name="toValue">Valeur de destination de l'animation.</param>
        /// <param name="duration">
        /// Durée nécessaire à l'animation pour accomplir son cycle en une seule fois.
        /// Pour plus d'informations, consultez la propriété <see cref="Duration"/>.
        /// </param>
        /// <param name="fillBehavior">Spécifie la manière dont l'animation doit se comporter lorsqu'elle n'est pas active.</param>
        public Vector3Animation(SlimDX.Vector3 toValue, Duration duration, FillBehavior fillBehavior)
        {
            this.Duration = duration;
            this.To = toValue;
            this.FillBehavior = fillBehavior;
        }

        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="Vector3Animation"/> qui s'anime de la valeur de début spécifiée à la valeur de destination définie sur la durée indiquée.
        /// </summary>
        /// <param name="fromValue">Valeur de début de l'animation.</param>
        /// <param name="toValue">Valeur de destination de l'animation.</param>
        /// <param name="duration">
        /// Durée nécessaire à l'animation pour accomplir son cycle en une seule fois.
        /// Pour plus d'informations, consultez la propriété <see cref="Duration"/>.
        /// </param>
        public Vector3Animation(SlimDX.Vector3 fromValue, SlimDX.Vector3 toValue, Duration duration)
            : this(fromValue, toValue, duration, FillBehavior.HoldEnd)
        { }

        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="Vector3Animation"/> qui s'anime de la valeur de début spécifiée à la valeur de destination définie sur la durée indiquée et dont le comportement de remplissage correspond à celui défini.
        /// </summary>
        /// <param name="fromValue">Valeur de début de l'animation.</param>
        /// <param name="toValue">Valeur de destination de l'animation.</param>
        /// <param name="duration">
        /// Durée nécessaire à l'animation pour accomplir son cycle en une seule fois.
        /// Pour plus d'informations, consultez la propriété <see cref="Duration"/>.
        /// </param>
        /// <param name="fillBehavior">Spécifie la manière dont l'animation doit se comporter lorsqu'elle n'est pas active.</param>
        public Vector3Animation(SlimDX.Vector3 fromValue, SlimDX.Vector3 toValue, Duration duration, FillBehavior fillBehavior)
        {
            this.From = fromValue;
            this.To = toValue;
            this.Duration = duration;
            this.FillBehavior = fillBehavior;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Obtient ou définit la valeur totale en fonction de laquelle l'animation modifie sa valeur de début. 
        /// </summary>
        public SlimDX.Vector3? By
        {
            get { return (SlimDX.Vector3?)GetValue(ByProperty); }
            set { SetValue(ByProperty, value); }
        }

        /// <summary>
        /// Obtient ou définit la fonction d'accélération appliquée à cette animation.
        /// </summary>
        public IEasingFunction EasingFunction
        {
            get { return (IEasingFunction)GetValue(EasingFunctionProperty); }
            set { SetValue(EasingFunctionProperty, value); }
        }

        /// <summary>
        /// Obtient ou définit la valeur de début de l'animation. 
        /// </summary>
        public SlimDX.Vector3? From
        {
            get { return (SlimDX.Vector3?)GetValue(FromProperty); }
            set { SetValue(FromProperty, value); }
        }

        /// <summary>
        /// Obtient ou définit la valeur de fin de l'animation. 
        /// </summary>
        public SlimDX.Vector3? To
        {
            get { return (SlimDX.Vector3?)GetValue(ToProperty); }
            set { SetValue(ToProperty, value); }
        }

        /// <summary>
        /// Obtient ou définit une valeur qui indique si la valeur actuelle de la propriété cible doit être ajoutée à la valeur de début de cette animation. 
        /// </summary>
        public bool IsAdditive 
        {
            get { return (bool)GetValue(IsAdditiveProperty); }
            set { SetValue(IsAdditiveProperty, value); }
        }

        /// <summary>
        /// Obtient ou définit une valeur qui spécifie si la valeur de l'animation est cumulée lorsqu'elle se répète. 
        /// </summary>
        public bool IsCumulative
        {
            get { return (bool)GetValue(IsCumulativeProperty); }
            set { SetValue(IsCumulativeProperty, value); }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Crée un clone modifiable de ce <see cref="Vector3Animation"/>, en créant des copies complètes des valeurs de cet objet.
        /// Lors de la copie de propriétés de dépendance, cette méthode copie les références de ressources et les liaisons de données (même si elles ne peuvent plus être résolues), mais pas les animations ni leurs valeurs actuelles.
        /// </summary>
        /// <returns>
        /// Clone modifiable de l'objet actuel.
        /// La valeur de la propriété <see cref="Freezable.IsFrozen"/> de l'objet cloné est false, même si la valeur de la propriété <see cref="Freezable.IsFrozen"/> de la source est true.
        /// </returns>
        public new Vector3Animation Clone()
        {
            return base.Clone() as Vector3Animation;
        }
        #endregion

        #region Vector3AnimationBase Implementation
        /// <summary>
        /// Calcule une valeur qui correspond à la valeur actuelle de la propriété en cours d'animation, tel que déterminé par l'animation hôte. 
        /// </summary>
        /// <param name="defaultOriginValue">Valeur d'origine suggérée, utilisée si la valeur de départ de l'animation n'est pas explicitement définie.</param>
        /// <param name="defaultDestinationValue">Valeur de destination suggérée, utilisée si la valeur de fin de l'animation n'est pas explicitement définie.</param>
        /// <param name="animationClock"><see cref="AnimationClock"/> qui génère le <see cref="Clock.CurrentTime"/> ou <see cref="Clock.CurrentProgress"/> utilisé par l'animation hôte.</param>
        /// <returns>Valeur calculée de la propriété, déterminée par l'animation actuelle.</returns>
        protected override SlimDX.Vector3 GetCurrentValueCore(SlimDX.Vector3 defaultOriginValue, SlimDX.Vector3 defaultDestinationValue, AnimationClock animationClock)
        {
            if (object.ReferenceEquals(animationClock, null))
                throw new ArgumentNullException("animationClock");

            if (!animationClock.CurrentProgress.HasValue) return SlimDX.Vector3.Zero;

            SlimDX.Vector3 returnValue;
            SlimDX.Vector3 start;
            SlimDX.Vector3 delta;

            if (this.From.HasValue && this.To.HasValue)
            {
                start = this.From.Value;
                delta = this.To.Value - start;
            }
            else if (this.From.HasValue && this.By.HasValue)
            {
                start = this.From.Value;
                delta = this.By.Value;
            }
            else if (this.From.HasValue)
            {
                start = this.From.Value;
                delta = defaultDestinationValue - start;
            }
            else if (this.To.HasValue)
            {
                start = defaultOriginValue;
                delta = this.To.Value - start;
            }
            else if (this.By.HasValue)
            {
                start = defaultOriginValue;
                delta = this.By.Value;
            }
            else
            {
                start = defaultOriginValue;
                delta = defaultDestinationValue - start;
            }

            double progress = this.EasingFunction != null ? this.EasingFunction.Ease(animationClock.CurrentProgress.Value) : animationClock.CurrentProgress.Value;
            returnValue = start + (float)progress * delta;

            if (IsAdditive && From != null && (To != null || By != null))
                returnValue += defaultOriginValue;

            if (IsCumulative && animationClock.CurrentIteration.HasValue)
                returnValue += (animationClock.CurrentIteration.Value - 1) * delta;

            return returnValue;
        }
        #endregion

        #region Freezable Implementation
        /// <summary>
        /// Une fois implémentée dans une classe dérivée, initialise une nouvelle instance de la classe dérivée <see cref="Freezable"/>. 
        /// </summary>
        /// <returns>Nouvelle instance.</returns>
        protected override Freezable CreateInstanceCore()
        {
            return new Vector3Animation();
        }
        #endregion
    }
}
